// Copyright 2011-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_WSTRING_CONVERSION_H_
#define LIBV_WSTRING_CONVERSION_H_

#include <libv/cast.h>
#include <libv/check.h>
#include <libv/static_assert.h>
#include <libv/stdint.h>
#include <string>



namespace libv {
namespace internal {

template<size_t TSizeOfWchar>
inline void PushBackCodePoint(const uint32_t, ::std::wstring*);

template<>
inline void PushBackCodePoint<2>(const uint32_t w, ::std::wstring* str) {
    LIBV_PRECOND(w <= 0x10FFFF && !(0xD800 <= w && w <= 0xDFFF));
    LIBV_PRECOND(str != NULL);

    if ( w >= 0x10000 ) {
        // http://unicode.org/faq/utf_bom.html
        str->push_back(IntegerCast<wchar_t>(0xD800 - 0x40 + ( w >> 10 )));
        str->push_back(IntegerCast<wchar_t>(0xDC00 + ( w & 0x3FF )));
    } else {
        str->push_back(libv::IntegerCast<uint16_t>(w));
    }
}

template<>
inline void PushBackCodePoint<4>(const uint32_t w, ::std::wstring* str) {
    LIBV_PRECOND(w <= 0x10FFFF);
    LIBV_PRECOND(str != NULL);

    str->push_back(static_cast<wchar_t>(w));
}



inline char ToU8(const int u8) {
    return static_cast<char>(static_cast<uint8_t>(u8));
}

}  // internal



// Длина utf-8-последовательности, начинающейся с индекса current_pos.
inline size_t Utf8RuneLength(const ::std::string& utf8_str, const size_t pos) {
    LIBV_PRECOND(pos < utf8_str.size());

    const uint8_t c(utf8_str[pos]);
    if ( c <= 0x7F ) {         // 1-байтовый
        return 1U;
    } else if ( c <= 0xBF ) {  // продолжение
        LIBV_VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
    } else if ( c <= 0xDF ) {  // начало 2-байтового
        return 2U;
    } else if ( c <= 0xEF ) {  // начало 3-байтового
        return 3U;
    } else if ( c <= 0xF7 ) {  // начало 4-байтового
        return 4U;
    } else {
        LIBV_VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
    }
    return false;  // недостижимая точка
}



// Декодирование utf-8-последовательности, начинающейся с позиции pos.
inline uint32_t Utf8RuneDecode(const ::std::string& utf8_str, size_t* pos) {
    LIBV_PRECOND(pos != NULL);

    uint32_t result = 0;
    size_t bytes_left = 0;

    for ( ; *pos < utf8_str.size() ; ++(*pos) ) {
        const uint8_t c = utf8_str[*pos];  // явно беззнаковое

        if ( c <= 0x7F ) {         // 1-байтовый
            LIBV_VERIFY(bytes_left == 0);
            ++(*pos);
            return c;

        } else if ( c <= 0xBF ) {  // продолжение
            LIBV_VERIFY(bytes_left > 0);
            result = ( ( result << 6 ) | ( c & 0x3F ) );
            if ( --bytes_left == 0 ) {
                LIBV_VERIFY(result <= 0x10FFFF);
                ++(*pos);
                return result;
            }

        } else if ( c <= 0xDF ) {  // начало 2-байтового
            LIBV_VERIFY(bytes_left == 0);
            bytes_left = 1;
            result = ( c & 0x1F );

        } else if ( c <= 0xEF ) {  // начало 3-байтового
            LIBV_VERIFY(bytes_left == 0);
            bytes_left = 2;
            result = ( c & 0x0f );

        } else if ( c <= 0xF7 ) {  // начало 4-байтового
            LIBV_VERIFY(bytes_left == 0);
            bytes_left = 3;
            result = ( c & 0x07 );

        } else {
            LIBV_VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
        }
    }
    LIBV_VERIFY_MSG(false, "Unexpected end of utf-8 sequence");
    return false;  // недостижимая точка
}



// Конвертация utf-8 строки в wstring (utf-16 или utf-32 в зависимости от
// платформы). В случае ошибки генерируется исключение libv::VerifyException.
inline ::std::wstring Utf8ToWstring(const ::std::string& utf8_str) {
    ::std::wstring result;
    result.reserve(utf8_str.size());

    for ( size_t i = 0 ; i < utf8_str.size() ; ) {
        const uint32_t rune = Utf8RuneDecode(utf8_str, &i);
        internal::PushBackCodePoint<sizeof(wchar_t)>(rune, &result);
    }

    return result;
}

inline void AppendCodepointToUtf8String(int cp, ::std::string* utf8_str) {
    LIBV_PRECOND(0 <= cp && cp <= 0x10FFFF);
    LIBV_PRECOND(utf8_str != NULL);

    static const unsigned char byte_mask = 0xBF;
    static const unsigned char byte_mark = 0x80;
    static const int first_byte_mark[] = { 0, 0x00, 0xC0, 0xE0, 0xF0 };

    size_t num_bytes;
    if ( cp <= 0x7F ) { 
        num_bytes = 1U;
    } else if ( cp <= 0x7FF ) {
        num_bytes = 2U;
    } else if ( cp <= 0xFFFF ) {
        num_bytes = 3U;
    } else {
        num_bytes = 4U;
    }

    utf8_str->append(num_bytes, 0);
    size_t n = utf8_str->size() - 1;

    using internal::ToU8;
    switch ( num_bytes ) {  // case-ы проваливаются!
        case 4: (*utf8_str)[n--] = ToU8((cp | byte_mark) & byte_mask); cp >>= 6;
        case 3: (*utf8_str)[n--] = ToU8((cp | byte_mark) & byte_mask); cp >>= 6;
        case 2: (*utf8_str)[n--] = ToU8((cp | byte_mark) & byte_mask); cp >>= 6;
        case 1: (*utf8_str)[n--] = ToU8(cp | first_byte_mark[num_bytes]);
    }
}

}  // libv

#endif  // LIBV_WSTRING_CONVERSION_H_
